library(plyr)
library(data.table)

### Globals -------------------------------------------------------------------

BETFAIR.COMMISSION <- 0.05
INITIAL.BANK <- 100
MAX.STAKE <- 500
PRP.STAKE <- 0.01

### Data functions ------------------------------------------------------------

TestObject <- function(object){
  exists(as.character(substitute(object)))
}

LoadData <- function (dataDir, existingData = UKHR){
  # Loads data, appends to existing data
  # BUG: due to memory issues when loading data, combine the csvs first
  # BUG: rbind.fill doesn't appear to work with data.table
  file.list = list.files(dataDir, full.names = TRUE, pattern = "[a-z]*.csv$")
  data <- ldply(file.list, .fun = read.csv, header = TRUE)
  data <- data.table(data)
  setkey(data, UKHR_RaceID, Horse)
  data <- PrepareData(data)
  if(TestObject(existingData))  (data <- rbind(existingData, data))
  return(data)
}

PrepareData <- function (data){
  # Prepares data for analysis
  
  # TODO: normalised place probabilities
  # TODO: dead heat P&L
  
  #remove duplicates (as per key)
  data <- unique(data)
  
  # Fixes
  data[, Stall.Percentage := as.numeric(sub("%", "", Stall.Percentage))]
  data[, Date := as.Date(Date, format = "%d/%m/%Y")]
  
  # Normalises value probabilities (eg where there are non-runners)
  temp <- data[,list(Total.Mkt.Value = sum(Value.Odds..Probability.),
                  Value.Odds..Probability.Norm = Value.Odds..Probability. / sum(Value.Odds..Probability.)),
            by=UKHR_RaceID]
  data[, Total.Mkt.Value := temp[, Total.Mkt.Value]]
  data[, Value.Odds..Probability.Norm := temp[, Value.Odds..Probability.Norm]]
  remove(temp)
  
  # Fix walkovers
  # TODO: delete instead
  data[WRITE.IN.S.P..HERE == "No Odds", Betfair.Win.S.P. := 1]
  data[WRITE.IN.S.P..HERE == "No Odds", Betfair.Place.S.P. := 1]

  # Insert missing win odds
  # modifiers as per DA Tool
  # warnings due to strings in the WRITE.IN.S.P..HERE column
  # (!) Place.S.P.mod can be less than 1
  BSP.modifier <- 1.2
  place.modifier <- 5
  data[, Win.S.P.mod := ifelse(is.na(Betfair.Win.S.P.),
                               as.numeric(as.character(WRITE.IN.S.P..HERE)) * BSP.modifier,
                               Betfair.Win.S.P.)]
  data[, Place.S.P.mod := ifelse(is.na(Betfair.Place.S.P.),
                                 as.numeric(as.character(WRITE.IN.S.P..HERE)) * BSP.modifier / place.modifier,
                                 Betfair.Place.S.P.)]
  
  # Insert Value ratios
  data[, Win.Value.Ratio := (Win.S.P.mod * Value.Odds..Probability.)]
  data[, Win.Value.Ratio.Norm := (Win.S.P.mod * Value.Odds..Probability.Norm)]
  data[, Place.Value.Ratio := (Place.S.P.mod * Value.Place.Odds)]
  data[, Win.Back.Edge := Win.Value.Ratio.Norm - 1]
  data[, Win.Lay.Edge := (1 - Value.Odds..Probability.Norm) * (Win.S.P.mod / (Win.S.P.mod - 1)) - 1]
  
  # Insert P&L
  data[, P_L.Win := ifelse(Result == 1, Win.S.P.mod - 1, -1)]
  data[, P_L.Place := ifelse(Betfair.Placed == 1, Place.S.P.mod - 1, -1)]
  
  # Insert Kelly bets
  data[, P_L.Kelly.Win := ifelse(Win.Back.Edge > 0,
                                 ifelse(Result == 1,
                                        Win.Back.Edge,
                                        - Win.Back.Edge / (Betfair.Win.S.P. - 1) ),
                                 0)]
  
  
  # Fudge the NAs
  data[is.na(Win.S.P.mod), Win.S.P.mod := 0]
  data[is.na(Place.S.P.mod), Place.S.P.mod := 0]
  data[is.na(Win.Back.Edge), Win.Back.Edge := 0]
  data[is.na(Win.Lay.Edge), Win.Lay.Edge := 0]
  data[is.na(P_L.Win), P_L.Win := 0]
  data[is.na(P_L.Place), P_L.Place := 0]
  data[is.na(P_L.Kelly.Win), P_L.Kelly.Win := 0]


  return(data)
}

### Selections ----------------------------------------------------------------

SelectData <- function(object, data = UKHR){
  # Returns subset of the data
  # TODO: create dump per market, although probably slower
  #       probably using data[, lapply(.SD, xxxx), by = UKHR_RaceID]
  data[eval(object)]
}

### Create Report -------------------------------------------------------------

# Profit after commission
NetCommission <- function(profit, betfairCommission = BETFAIR.COMMISSION) {
  ifelse(profit > 0, profit * (1 - betfairCommission), profit)
}

MakeReport <- function(data,
                       place = FALSE,
                       lay = FALSE,
                       betfairCommission = BETFAIR.COMMISSION,
                       initialBank = INITIAL.BANK,
                       maxStake = MAX.STAKE,
                       prpStake = PRP.STAKE) {

  if (nrow(data) > 0) {

    dataByMkt <- data[, list(P_L.Win = sum(P_L.Win),
                             P_L.Place = sum(P_L.Place),
                             P_L.Kelly.Win = sum(P_L.Kelly.Win)),
                      by = UKHR_RaceID]
    
    if (place) dataByMkt[, P_L:= P_L.Place] else dataByMkt[, P_L:= P_L.Win]
    if (lay) dataByMkt[, P_L:= - P_L]
    dataByMkt[, P_L:= NetCommission(P_L)]
    dataByMkt[, Result := P_L > 0]
    
    # Set up output
    z <- list()
    
    z$Wins <- dataByMkt[, sum(Result == 1)]
    z$Runs <- nrow(dataByMkt)
    z$SR <- round(100 * z$Wins / z$Runs, 1)
    
    ### Level Stakes
    
    dataByMkt[, Lvl.Bank := initialBank + cumsum(P_L)]  # cumulative P&L
    dataByMkt[, Lvl.Highest.Point := cummax(pmax(initialBank, Lvl.Bank))]  # running max
    z$Lvl.Bank <- round(dataByMkt[z$Runs, Lvl.Bank], 2)
    z$Lvl.Largest.Drop <- round(dataByMkt[, max(Lvl.Highest.Point - Lvl.Bank)], 2)
    z$Lvl.Largest.Drop.Percentage <- round(100 *
      dataByMkt[, max((Lvl.Highest.Point - Lvl.Bank) / Lvl.Highest.Point)], 1)
    z$Lvl.ROI <- round(100 * (z$Lvl.Bank - initialBank) / z$Runs, 1)
    z$Sharpe.Ratio <- round(dataByMkt[, mean(P_L) / sd(P_L)], 3)
    
    ### Proportional stakes
    
    # Method 1 - total time 2.8 seconds
    #  dataByMkt[, Prp.Stake := 0]
    #  dataByMkt[, Prp.Bank := 0]
    #  dataByMkt[1, Prp.Stake := initialBank * prpStake]
    #  dataByMkt[1, Prp.Bank := initialBank + Prp.Stake * P_L]
    #  for (n in 2:z$Runs) {
    #    dataByMkt[n, Prp.Stake := min(dataByMkt[n-1, Prp.Bank] * prpStake, maxStake)]
    #    dataByMkt[n, Prp.Bank := dataByMkt[n-1, Prp.Bank] + Prp.Stake * P_L]
    #  } 
    #  dataByMkt[, Prp.Stake := NULL]
    
    # Method 2 - total time 1.2 seconds
    #  dataByMkt[, Prp.Stake := 0]
    #  dataByMkt[, Prp.Bank := 0]  
    #  dPrpBank <- initialBank
    #  for (n in 1:z$Runs) {
    #    dPrpStake <- min(dPrpBank * prpStake, maxStake)
    #    dPrpBank <- dPrpBank + dPrpStake * dataByMkt[n, P_L]
    #    dataByMkt[n, Prp.Bank := dPrpBank]
    #  }
    #  dataByMkt[, Prp.Stake := NULL]  
    
    # Method 3 - total time < 0.1 seconds
    # Only valid if not hitting max stake, otherwise use method 2
    
    dataByMkt[, Prp.Bank := initialBank * cumprod((P_L + 100)/100)]
    
    if (dataByMkt[, max(Prp.Bank)] > maxStake / prpStake) {
      dPrpBank <- initialBank
      for (n in 1:z$Runs) {
        dPrpStake <- min(dPrpBank * prpStake, maxStake)
        dPrpBank <- dPrpBank + dPrpStake * dataByMkt[n, P_L]
        dataByMkt[n, Prp.Bank := dPrpBank]
      }
    }
    
    dataByMkt[ , Prp.Highest.Point := cummax(pmax(initialBank, Prp.Bank))]
    
    z$Prp.Bank <- round(dataByMkt[z$Runs, Prp.Bank], 2)
    z$Prp.Largest.Drop <- round(dataByMkt[, max(Prp.Highest.Point - Prp.Bank)], 2)
    z$Prp.Largest.Drop.Percentage <- round(100 *
      dataByMkt[, max((Prp.Highest.Point - Prp.Bank) / Prp.Highest.Point)], 1)
    z$Prp.ROI <- round(100 * (z$Prp.Bank - initialBank) / z$Runs, 1)

    z$KellyFlat <- dataByMkt[, sum(P_L.Kelly.Win, na.rm = TRUE)]
    z$KellyOpt <- dataByMkt[, sum(P_L.Kelly.Win, na.rm = TRUE) / sum(P_L.Kelly.Win ^ 2, na.rm = TRUE)]
    
    
    ### Remove data columns
    
    dataByMkt[, Lvl.Bank := NULL]
    dataByMkt[, Lvl.Highest.Point := NULL]
    dataByMkt[, Prp.Bank := NULL]
    dataByMkt[, Prp.Highest.Point := NULL]
    dataByMkt[, P_L:= NULL]
  
    return(z)
  }
}

### Run multiple reports ------------------------------------------------------

MakeReports <- function(data,
                         place = FALSE,
                         lay = FALSE,
                         betfairCommission = BETFAIR.COMMISSION,
                         initialBank = INITIAL.BANK,
                         maxStake = MAX.STAKE,
                         prpStake = PRP.STAKE) {
  
  if (place) data[, Value.Ratio := Place.Value.Ratio] else data[, Value.Ratio := Win.Value.Ratio]
  if (lay) data[, Value.Ratio := 1 / Value.Ratio]
  
  # Load reports
  reports <- read.csv("reports.csv")
  reports <- data.table(reports)
  setkey(reports, Report.Number)

  result <- reports[, MakeReport(data[eval(parse(text = as.character(Expression))),
                                      list(UKHR_RaceID,
                                           Result,
                                           P_L.Win,
                                           P_L.Place,
                                           P_L.Kelly.Win)],
                                 place,
                                 lay,
                                 betfairCommission,
                                 initialBank,
                                 maxStake,
                                 prpStake),
                    by = Report.Number]

  # add back the report parameters
  result <- result[reports]
  # result <- reports[result] # leaves out blank reports
  
  # create year and month reports
  dateReports <- MakeDateReports(data, place, lay, betfairCommission, initialBank, maxStake, prpStake)
  result <-  suppressWarnings(rbind(result, dateReports))
  
  #remove redundant columns
  result[, Expression := NULL]
  data[, Value.Ratio := NULL]
  return(result)
}

### Run multiple reports by race type and betting market ----------------------

MakeAllReports <- function(data,
                        place = FALSE,
                        lay = FALSE,
                        betfairCommission = BETFAIR.COMMISSION,
                        initialBank = INITIAL.BANK,
                        maxStake = MAX.STAKE,
                        prpStake = PRP.STAKE) {


  race.types <- c("AW", "Flat", "NH Flat", "Hurdle", "Chase")

  # First run, win, all types
  reports <- MakeReports(data, place = FALSE, lay, betfairCommission, initialBank, maxStake, prpStake)
  reports[ , Report.Number := 100000 + Report.Number]
  reports[ , Description2 := "Win"]

  # win, each type
  for (n in 1:length(race.types)){
    d.filter <- data[Race.Type == race.types[n], ]
    if (nrow(d.filter) > 0) {
      temp <- MakeReports(d.filter, place = FALSE, lay, betfairCommission, initialBank, maxStake, prpStake)
      temp[ , Report.Number := 100000 + n*10000 + Report.Number]
      temp[ , Description2 := paste("Win", race.types[n], sep = " ")]
      reports <- rbind(reports, temp)
      remove(temp)
    }
  }

  # place reports
  if (place) {
    # all types
    temp <- MakeReports(data, place, lay, betfairCommission, initialBank, maxStake, prpStake)
    temp[ , Report.Number := 200000 + Report.Number]
    temp[ , Description2 := "Place"]
    reports <- rbind(reports, temp)

    # each type
    for (n in 1:length(race.types)){
      d.filter <- data[Race.Type == race.types[n], ]
      if (nrow(d.filter) > 0) {
        temp <- MakeReports(d.filter, place, lay, betfairCommission, initialBank, maxStake, prpStake)
        temp[ , Report.Number := 200000 + n*10000 + Report.Number]
        temp[ , Description2 := paste("Place", race.types[n], sep = " ")]
        reports <- rbind(reports, temp)
        remove(temp)
      }
    }
  }
  return(reports)
}

MakeDateReports <- function (data,
                         place = FALSE,
                         lay = FALSE,
                         betfairCommission = BETFAIR.COMMISSION,
                         initialBank = INITIAL.BANK,
                         maxStake = MAX.STAKE,
                         prpStake = PRP.STAKE) {

  # create range of possible years and year_months
  yyRange <- data[, list(Description = format(seq(min(Date), to = max(Date), by="1 year"), "%Y"))]
  yymmRange <- data[, list(Description = format(seq(min(Date), to = max(Date), by="1 month"), "%Y-%m"))]
  
  ## make reports for each possible point in the range
  
  # years
  resultYY <- yyRange[, MakeReport(data[format(Date, "%Y") == Description],
                                   place,
                                   lay,
                                   betfairCommission,
                                   initialBank,
                                   maxStake,
                                   prpStake), by = Description]
  resultYY[, Report.Block := 35]
  
  #months
  resultYYMM <- yymmRange[, MakeReport(data[format(Date, "%Y-%m") == Description],
                                       place,
                                       lay,
                                       betfairCommission,
                                       initialBank,
                                       maxStake,
                                       prpStake), by = Description]
  # create report rows for blank reports
  setkey(resultYYMM, Description)
  resultYYMM <- resultYYMM[yymmRange]
  resultYYMM[, Report.Block := 36]
  
  result <- rbind(resultYY, resultYYMM)
  # insert missing report columns
  result[, Report := NA]
  result[, Report.Number := NA]
  result[, Expression := NA]
  
  return(result)
}